fix(cloudflare-node): use native IdentityTransformStream to prevent intermittent mid-stream response stalls#1196
Open
Einherjar1632 wants to merge 1 commit into
Conversation
… body The JS-backed ReadableStream with a manually captured controller intermittently stalls mid-stream on deployed Workers: the final flushes are never delivered to the client and the terminating chunk is never sent, leaving connections open indefinitely. Writes were acknowledged without backpressure, so the stall was invisible to the worker. Switch to the Workers-native IdentityTransformStream and await writer.write() so the runtime pumps the stream with real backpressure.
🦋 Changeset detectedLatest commit: ff98bd4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
cloudflare-nodewrapper builds the streamed response body from a JS-backedReadableStreamwith a manually captured controller, andWritable.writeacknowledges chunks immediately without backpressure (controller.desiredSizeis never consulted).On deployed Workers (production runtime, not reproducible with
wrangler dev/local workerd) we observed this intermittently stalling mid-stream: the final flush(es) of chunked SSR/RSC responses are never delivered to the client and the terminating chunk is never sent, so the connection stays open indefinitely. Browsers keep waiting on the incomplete response, gradually exhaust their per-origin connection pool, and the whole site eventually freezes for that browser profile (every request stuck in "Stalled", even sign-out).This PR switches the response body to the Workers-native (C++-backed)
IdentityTransformStreamand awaitswriter.write(), so the runtime pumps the stream itself with real end-to-end backpressure.Reproduction / evidence
Observed on a production app (Next.js
16.2.10,@opennextjs/cloudflare1.20.1,wrangler4.107.0,compatibility_date2025-12-01, free plan, custom domain). No auth required — a plain RSC request to the login page reproduces it:wrangler tailshows no event at all for hung requests (the invocation never completes); completed requests logoutcome: okwith 8–22ms CPU, so this is notexceededCpu.wrangler dev/miniflare (0/30 with the same build), so it appears to be an interaction between the JS-backed stream pump and the production runtime — possibly related to recent workerd stream regressions (e.g. 🐛 Bug Report — Runtime APIs: client disconnect no longer cancels async ReadableStream response body (regressed in 1.20260619.1, persists in 1.20260701.1) cloudflare/workerd#6832, Errored response-body streams terminate cleanly instead of aborting, silently truncating the response cloudflare/workerd#6818), but the wrapper-side pattern makes it invisible: writes are acked without backpressure, so nothing ever notices the pump stalled.Validation
Measured on the same production deployment, patched adapter only (no other changes):
pnpm build(tsc) passes,pnpm lint(biome) clean,tests-unit600/600 pass.IdentityTransformStreamis declared locally in the file (it is a Workers-specific global); this wrapper only runs on the Workers runtime.Notes
writer.write()resolving only when the chunk is accepted also gives the wrapper real backpressure, which the previous implementation lacked (unbounded queue growth in the JS stream).Uint8Arraychunks are converted withBuffer.from(chunk, encoding)sinceIdentityTransformStreamonly accepts bytes.🤖 Generated with Claude Code